/**
 * @file memory.c
 * @author lcz (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2022-10-26
 *
 * @copyright Copyright (c) 2022
 *
 */

#include "core/memory.h"
#include "tools/bitmap.h"
#include "ipc/mutex.h"
#include "comm/boot_info.h"
#include "tools/kilb.h"
#include "cpu/mmu.h"
#include "dev/console.h"

static addr_alloc_t paddr_alloc; // 物理地址分配结构
static pde_t kernel_page_dir[PDE_CNT] __attribute__((aligned(MEM_PAGE_SIZE)));
// static pte_t kernel_page_dir2[PDE_CNT] __attribute__((aligned(MEM_PAGE_SIZE)));
/**
 * @brief 初始化地址分配结构
 * 以下不检查start和size的页边界，由上层调用者检查
 */
static void addr_alloc_init(addr_alloc_t *alloc, uint8_t *bits, uint32_t start, uint32_t size, uint32_t page_size)
{
    mutex_init(&alloc->mutex);
    alloc->start = start;
    alloc->size = size;
    alloc->page_size = page_size;
    // init mutex start size page_size bitmap
    bitmap_init(&alloc->bitmap, bits, (alloc->size / page_size), 0);
}

/**
 * @brief 申请内存页(申请的是真实的物理空间哦！！)
 *
 * @param alloc
 * @param page_count 申请多少页
 * @return uint32_t  申请到的内存地址，连续的首地址
 */
uint32_t addr_alloc_page(addr_alloc_t *alloc, int page_count)
{

    uint32_t addr = 0;
    mutex_lock(&alloc->mutex);
    //记得互斥锁一下
    int page_index = bitmap_alloc_nbits(&alloc->bitmap, 0, page_count);

    if (page_index >= 0)
    {
        // 申请成功
        addr = alloc->start + page_index * alloc->page_size;
    }
    mutex_unlock(&alloc->mutex);

    return addr;
}

/**
 * @brief 释放申请的内存
 *
 * @param alloc
 * @param page_count 释放的页数
 */
void addr_free_page(addr_alloc_t *alloc, uint32_t addr, int page_count)
{
    // 同样上锁与解锁
    mutex_lock(&alloc->mutex);
    uint32_t page_index = (addr - alloc->start) / alloc->page_size;
    bitmap_set_bit(&alloc->bitmap, page_index, page_count, 0);
    mutex_unlock(&alloc->mutex);
}
/**
 * @brief 打印内存信息
 * @param boot_info 
 * @return * void 
 */
void show_mem_info(boot_info_t *boot_info)
{
    log_printf("mem region:");
    for (int i = 0; i < boot_info->ram_region_count; i++)
    {
        log_printf("[%d]:0x%x-0x%x", i, boot_info->ram_region_cfg[i].start, boot_info->ram_region_cfg[i].size);
    }
    log_printf("\n");
}
/**
 * @brief 获取可用的物理内存大小
 */
static uint32_t total_mem_size(boot_info_t *boot_info)
{
    uint32_t mem_size = 0;
    for (int i = 0; i < boot_info->ram_region_count; i++)
    {
        mem_size += boot_info->ram_region_cfg[i].size;
    }
    return mem_size;
}

/**
 * @brief 页目录加线性地址找该线性地址对应的页表，将二级页表映射到指定物理内存，并回填一级页表
 *
 * @param page_dir 页目录地址
 * @param vaddr 线性地址
 * @param allo 为0在二级目录项（实际上时二级页表）不存在的情况下不分配二级页表
 * @return * pte_t*
 */
pte_t *find_pte(pde_t *page_dir, uint32_t vaddr, int allo)
{
    // 二级页表
    pte_t *page_table;
    // 有页目录找第二级页表项，根据页目录基址+缩影得到二级页表项地址
    // 找到页目录中对应的页表项
    pde_t *pde = page_dir + pde_index(vaddr);
    if (pde->present)
    {
        // 获取二级页表
        page_table = (pte_t *)pde_paddr(pde);
    }
    else
    {
        // 二级页表不存在且不申请，直接放回
        if (allo == 0)
        {
            return (pte_t *)0;
        }
        // 申请内存页作为二级页表
        uint32_t pg_paddr = addr_alloc_page(&paddr_alloc, 1);
        // 没申请到内存页作为二级页表
        if (pg_paddr == 0)
        {
            return (pte_t *)0;
        }
        // 申请到了二级页表在页目录中设置权限 pg_paddr低12位0
        // 将申请到的二级页表注册到页目录中，同时页目录该页目录项有效位设置为1，有效
        pde->v = pg_paddr | PDE_P | PDE_W | PDE_U;
        // pde->v = pg_paddr | PDE_P;

        // 申请到了，现在初始化一下,二级页表清空
        // 对应的二级页表项内容清空，一整个页表内容清空
        page_table = (pte_t *)pg_paddr;
        kernel_memset(page_table, 0, MEM_PAGE_SIZE);
    }
    // 找到pte表项,那么此处放回的便是二级页表项，由页目录查出，查不到就申请后全部写入0就行
    return page_table + pte_index(vaddr);
}
/**
 * @brief 在指定的页目录中，将指定的线性地址映射到指定的物理页中
 *
 * @param page_dir 指定的页目录
 * @param vaddr 指定的线性地址
 * @param paddr 指定的物理页
 * @param count 映射的页数
 * @param perm 权限
 * @return * int
 */
int memory_create_map(pde_t *page_dir, uint32_t vaddr, uint32_t paddr, int count, uint32_t perm)
{
    // 现在就是所前面到这里要你到内存申请count物理内存
    for (int i = 0; i < count; i++)
    {
        // log_printf("create_map: v - 0x %x, p - 0x %x, perm : 0x %x", vaddr, paddr, perm);
        // page_dir线性地址的开始，也开始地址,现在就是根据页目录找一下那个那个第二级的页表项有无了  vaddr 虚拟地址
        // paddr物理地址
        pte_t *pte = find_pte(page_dir, vaddr, 1);
        if (pte == (pte_t *)0)
        {
            log_printf("create pte failed.pte==0");
            return -1;
        }
        // log_printf("pte addr:0x%x", (uint32_t)pte);
        // 创建映射的时候，这条pte应当是不存在的。
        // 如果存在，说明可能有问题（此处如果跑kernel代码，kernal代码映射回自己的地址，自己的地址本来就有东西，因此这个判断宏大概路失败，故取消值）
        // ASSERT(pte->present == 0);
        // pte表项的值指向对应的物理地址，并添加了权限，则建立起了映射关系
        pte->v = paddr | perm | PTE_P;
        vaddr += MEM_PAGE_SIZE;
        paddr += MEM_PAGE_SIZE;
    }
}
/**
 * @brief 根据内存映射表，构造内核页表
 */
void create_kernel_page_table(void)
{
    // 将连接器中的地址写成数组，数组地址就是那个变量的地址
    // 这块地址的映射仍然需要进行一一映射
    extern uint8_t s_text[], e_text[], s_data[];
    extern uint8_t kernel_base[];

    memory_map_t kernel_map[] = {
        // 这样实际上映射回了自己，:) :)
        {kernel_base, s_text, kernel_base, PTE_W},
        {s_text, e_text, s_text, 0},
        {s_data, (void *)MEM_EBDA_START, s_data, PTE_W},
        {(void *)CONSOLE_DISP_ADDR, (void *)CONSOLE_DISP_END,(void *)CONSOLE_DISP_ADDR, PTE_W},
        {(void *)MEM_EXT_START, (void *)MEM_EXT_END, (void *)MEM_EXT_START, PTE_W},
    };

    // 扫描 遍历表
    for (int i = 0; i < sizeof(kernel_map) / sizeof(memory_map_t); i++)
    {
        memory_map_t *map = kernel_map + i;
        // 初始化函数，初始化映射关系到表里面
        // 边界对齐
        uint32_t vstart = down2((uint32_t)map->vstart, MEM_PAGE_SIZE);
        uint32_t paddr = down2((uint32_t)map->pstart, MEM_PAGE_SIZE);
        uint32_t vend = up2((uint32_t)map->vend, MEM_PAGE_SIZE);
        // 计算总共多少页
        int page_count = (vend - vstart) / MEM_PAGE_SIZE;
        log_printf("first:%d segment,need %d page", i, page_count);
        memory_create_map(kernel_page_dir, vstart, (uint32_t)map->pstart, page_count, map->perm);
    }
}

/**
 * @brief 内存初始化
 *
 * @param boot_info
 */
void memory_init(boot_info_t *boot_info)
{
    // 声明链接器中的变量,位图地址
    extern uint8_t *mem_free_start;
    // 获取指针
    uint8_t *mem_free = (uint8_t *)&mem_free_start;
    log_printf("mem init");
    show_mem_info(boot_info);
    // 略有不同 此处多留意这部分内容， 舍弃1M内存给kernel使用，但可用内存第一部分从
    // 0x00000000 开始到0xf... 内容不到1M 我们没有放弃使用这部分内容，仍然使用
    uint32_t mem_up1MB_free = total_mem_size(boot_info) - MEM_EXT_START;
    // 多少页
    mem_up1MB_free = down2(mem_up1MB_free, MEM_PAGE_SIZE);
    log_printf("Free memory: 0x%x, size: 0x%x", MEM_EXT_START, mem_up1MB_free);
    addr_alloc_init(&paddr_alloc, mem_free, MEM_EXT_START, mem_up1MB_free, MEM_PAGE_SIZE);
    // 跳过位图的位置
    // 4GB大小需要总共4*1024*1024*1024/4096/8=128KB的位图, 使用低1MB的RAM空间中足够
    // 该部分的内存仅跟在mem_free_start开始放置
    mem_free += bitmap_byte_count(paddr_alloc.size / MEM_PAGE_SIZE);

    // 到这里，mem_free应该比EBDA地址要小
    ASSERT(mem_free < (uint8_t *)MEM_EBDA_START);

    create_kernel_page_table();
    mmu_set_page_dir((uint32_t)kernel_page_dir);
    /*
     测试代码
    addr_alloc_t addr_alloc;
    // 内存位图重新申请一块空间，这个空间将绑定到指定的一段内存区域，及addr_alloc->start处，
    // 后面还有考虑对申请这些内存页进程进行隔离，后面的事情
    uint8_t bits[8];
    // 测试申请64页内存，一页4k大小，基地址位0x1000
    addr_alloc_init(&addr_alloc, bits, 0x1000, 64 * 4096, 4096);
    for (int i = 0; i < 32; i++)
    {
        uint32_t addr = addr_alloc_page(&addr_alloc, 2);
        log_printf("alloc addr:0x%x", addr);
    }

    uint32_t addr = 0x1000;
    for (int i = 0; i < 32; i++)
    {
        addr_free_page(&addr_alloc, addr, 2);
        addr += 2*4096;
        log_printf("alloc addr:0x%x", addr);
    }
    */
}
/**
 * @brief 申请一页内存作为目录页
 *
 * @return * uint32_t 新申请的目录页的地址
 */
uint32_t memory_create_uvm(void)
{
    pde_t *page_dir = (pde_t *)addr_alloc_page(&paddr_alloc, 1);
    if (page_dir == 0)
    {
        return 0;
    }

    kernel_memset((void *)page_dir, 0, MEM_PAGE_SIZE);
    // 页表进行初始化，现在映射os代码区
    uint32_t user_pde_start = pde_index(MEMORY_TASK_BASE);
    // 存储空间有点浪费，每个进程都分配相同的0x8000 0000的地址
    // 为了不浪费，现在就简单的将页目录指向os的页目录，后续再为os的页目录指定一个0x0 - 0xffffffff的地址空间，进程后续再调用系统调用，（简单的存储共享的区域）
    for (int i = 0; i < user_pde_start; i++)
    {
        page_dir[i].v = kernel_page_dir[i].v;
    }
    // 0x8000 0000以上地址设置

    return (uint32_t)page_dir;
}
/**
 * @brief 给定页目录，在页目录的指定虚拟地址完成内存分配及映射填写
 *
 * @param page_dir 目录页
 * @param vaddr  虚拟地址
 * @param size 大小
 * @param perm 权限
 * @return * int
 */
int memory_alloc_for_page_dir(uint32_t page_dir, uint32_t vaddr, uint32_t size, int perm)
{
    uint32_t curr_vaddr = vaddr;
    // 只能大不能小
    int page_count = up2(size, MEM_PAGE_SIZE) / MEM_PAGE_SIZE;

    for (int i = 0; i < page_count; i++)
    {
        uint32_t paddr = addr_alloc_page(&paddr_alloc, 1);
        if (paddr == 0)
        {
            log_printf("mem alloc failed,no memory");
            return 0;
        }
        // 申请到一页内存就建立一页内存的映射
        int err = memory_create_map((pde_t *)page_dir, curr_vaddr, paddr, 1, perm);
        if (err < 0)
        {
            // 一个请求失败的话前面的映射关系页要取消的好吧
            log_printf("create memory failed. err=%d", err);
            addr_free_page(&paddr_alloc, vaddr, i + 1);
            return 0;
        }

        curr_vaddr += MEM_PAGE_SIZE;
    }

    return 0;
}

/**
 * @brief
 *
 */

/**
 * @brief 当前进程对应的页目录将线性地址映射到页目录中
 * 相同的线性地址，他们查找各自的页目录的相同索引，相同索引指向一个二级页表，他们查找各自二级页表的相同索引，二级页表的相同索引对应的物理地址不同
 *
 * @param addr 线性地址映射
 * @param size 大小
 * @param perm 权限
 * @return * int
 */
int memory_alloc_page_for(uint32_t addr, uint32_t size, int perm)
{
    return memory_alloc_for_page_dir(task_current()->tss.cr3, addr, size, perm);
}
/**
 * @brief 获取当前进程的目录页
 * 
 * @return * pde_t* 
 */
static pde_t *curr_page_dir(void)
{
    return (pde_t *)(task_current()->tss.cr3);
}
/**
 * @brief 为指定的虚拟地址空间分配多页内存
 */
uint32_t memory_alloc_page(void)
{
    uint32_t addr = addr_alloc_page(&paddr_alloc, 1);
    return addr;
    // 返回地址物理地址，物理地址有一个相等值对应的虚拟地址
}
/**
 * @brief 释放一页内存
 */
void memory_free_page(uint32_t addr)
{
    if (addr < MEMORY_TASK_BASE)
    {
        addr_free_page(&paddr_alloc, addr, 1);
    }
    else
    {
        pte_t *pte = find_pte(curr_page_dir(), addr, 0);
        ASSERT((pte != (pte_t *)0) && pte->present);

        addr_free_page(&paddr_alloc, pte_paddr(pte), 1);
        pte->v = 0;
    }
}

/**
 * @brief 销毁指定页目录的表项
 *
 * @param page_dir 将销毁的页目录
 * @return * void
 */
void memory_destory_uvm(uint32_t page_dir)
{
    uint32_t user_pde_start = pde_index(MEMORY_TASK_BASE);
    pde_t *pde = (pde_t *)page_dir + user_pde_start;
    for (int i = user_pde_start; i < PDE_CNT; i++, pde++)
    {
        if (!pde->present)
        {
            continue;
        }
        pte_t *pte = (pte_t *)pde_paddr(pde);
        for (int j = 0; j < PTE_CNT; j++, pte++)
        {
            if (!pte->present)
            {
                continue;
            }
            // 二级页目录表项
            addr_free_page(&paddr_alloc, (uint32_t)pte_paddr(pte), 1);
        }
        // 释放一级页目录表项
        addr_free_page(&paddr_alloc, pde_paddr(pde), 1);
    }
    //释放页目录表，没有对应的映射关系，物理页相当于没有使用，不用释放
    addr_free_page(&paddr_alloc, page_dir, 1);
}
/**
 * @brief 复制指定目录页中的物理的页到新申请的目录页中，此函数需要申请目录页，二级页表，物理页，并拷贝
 * 物理页内容
 *
 * @param page_dir 被复制的目录页
 * @return * uint32_t 新申请的目录页地址
 */
uint32_t memory_copy_uvm(uint32_t page_dir)
{
    uint32_t to_page_dir = memory_create_uvm();
    // 申请失败
    if (to_page_dir == 0)
    {
        goto copy_uvm_failed;
    }
    // 获取进程空间的目录页的起始缩影
    uint32_t user_pde_start = pde_index(MEMORY_TASK_BASE);
    pde_t *pde = (pde_t *)page_dir + user_pde_start;
    // PDE_CNT = 1024，目录页表项数
    for (int i = user_pde_start; i < PDE_CNT; i++, pde++)
    {
        if (!pde->present)
        {
            continue;
        }

        pte_t *pte = (pte_t *)pde_paddr(pde);
        for (int j = 0; j < PTE_CNT; j++, pte++)
        {
            if (!pte->present)
            {
                continue;
            }
            // 如果存在,申请内存空间进行映射
            uint32_t page = addr_alloc_page(&paddr_alloc, 1);
            if (page == 0)
            {
                goto copy_uvm_failed;
            }
            // 映射的线性地址  (i << 22) 目录页（一级页表项）索引  (j << 12)（二级页表项）索引
            uint32_t vaddr = (i << 22) | (j << 12);
            int err = memory_create_map((pde_t *)to_page_dir, vaddr, page, 1, get_pte_perm(pte));
            if (err < 0)
            {
                goto copy_uvm_failed;
            }
            // 建立映射后开始复制父进程的物理页，映射完一页拷贝一页
            kernel_memcpy((void *)page, (void *)vaddr, MEM_PAGE_SIZE);
        }
    }

    return to_page_dir;

copy_uvm_failed:
    if (to_page_dir)
    {
        memory_destory_uvm(to_page_dir);
    }

    return -1;
}
/**
 * @brief 放回虚拟地址在页表中对应的物理地址
 *
 * @param page_dir
 * @param vaddr
 * @return * uint32_t
 */
uint32_t memory_get_paddr(uint32_t page_dir, uint32_t vaddr)
{
    pte_t *pte = find_pte((pde_t *)page_dir, vaddr, 0);
    if (!pte)
    {
        return 0;
    }

    //
    return pte_paddr(pte) + (vaddr & (MEM_PAGE_SIZE - 1));
}

/**
 * @brief 拷贝在内存映射之间
 *
 * @param to 目的地址
 * @param page_dir 目录页
 * @param from 源地址
 * @param size 大小
 * @return * int
 */
int memory_copy_uvm_data(uint32_t to, uint32_t page_dir, uint32_t from, uint32_t size)
{
    while (size > 0)
    {
        // 页目录没有启用，只能直接找页表，注意页目录中的映射关系已经完成了，但是页目录还没有启用，页还不能用
        uint32_t to_paddr = memory_get_paddr(page_dir, to);
        if (to_paddr == 0)
        {
            return -1;
        }
        // to的offset
        // 祖业的拷贝
        uint32_t offset_in_page = to_paddr & (MEM_PAGE_SIZE - 1);
        uint32_t curr_size = MEM_PAGE_SIZE - offset_in_page;
        if (curr_size > size)
        {
            curr_size = size; // 如果比较大，超过页边界，则只拷贝此页内的
        }
        // 拷贝有个物理地址与其相同，在内核代码段好像
        kernel_memcpy((void *)to_paddr, (void *)from, curr_size);
        size -= curr_size;
        to += curr_size;
        from += curr_size;
    }
    return 0;
}
/**
 * @brief 调整堆的内存分配，返回堆之前的指针
 */
char *sys_sbrk(int incr)
{
    task_t *task = task_current();
    // 好吧 目前我们的堆空间只大不小
    char *pre_heap_end = (char *)task->heap_end;
    int pre_inc = incr;
    ASSERT(incr >= 0)
    if (incr == 0)
    {
        log_printf("sbrk(0):end=%x", pre_heap_end);
        return pre_heap_end;
    }

    uint32_t start = task->heap_end;
    uint32_t end = start + incr;
    // 0x81001024-0x81001024 需要页边界对齐
    int start_offset = start % MEM_PAGE_SIZE; // 0x24
    // 没在页的开头，不用申请内存，在这个页面上分配就行
    if (start_offset)
    {
        if (start_offset + incr <= MEM_PAGE_SIZE)
        {
            task->heap_end = end;
            return (char *)task->heap_end;
        }
        else
        {
            // 当前页不够分配
            uint32_t curr_size = MEM_PAGE_SIZE - start_offset;
            start += curr_size;
            incr -= curr_size;
            // 可以递归调用吗？？ 不可以直接递归没有申请内存
        }
    }
    // 剩下的部分连续分配 统一申请内存并完成映射
    if (incr)
    {
        uint32_t curr_size = end - start;
        // 从start开始分配，curr_size分配大小
        int err = memory_alloc_page_for(start, curr_size, PTE_P | PTE_U | PTE_W);
        if (err < 0)
        {
            log_printf("sbrk:alloc mem failed. ");
            return (char *)-1;
        }
    }
    log_printf("sbrk(%d):end=0x%x", pre_inc, end);
    task->heap_end = end;
    // 返回之前堆的末端地址
    return (char *)pre_heap_end;
}